/*
 * PathNormal.java
 *
 * Created on November 18, 2007, 11:50 AM
 *
 * Copyright (C) 2007-2010  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.wave;

import com.twolattes.json.Entity;
import com.twolattes.json.Value;
import hextd.*;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RadialGradientPaint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import com.usoog.tdcore.gamegrid.GameGrid;
import com.usoog.tdcore.gamegrid.GridPoint;
import com.usoog.tdcore.gamegrid.ScaledCanvas;
import com.usoog.tdcore.gamestate.GameState;
import com.usoog.tdcore.pathfinder.PathLocation;
import com.usoog.tdcore.player.Player;

/**
 *
 * @author hylke
 */
@Entity
public class PathNormal implements PathfinderWaypoints {

	private GameState gs;
	private Waypoint[] steps;
	private ScaledCanvas canvas;
	// Painting stuff
	private static Color blipColor = new Color(1.0f, 0.7f, 0.7f, 1f);
	private static Color blipBackColor = new Color(1.0f, 0.7f, 0.7f, 0f);
	private static Color lineColor = new Color(0.7f, 0.7f, 0.7f, 0.5f);
	private Stroke stroke;
	private Shape blipShape;
	private Paint blipFill;
	private double blipSize;
	private BufferedImage[] blipImage;
	private int[] blipStep;
	AffineTransform blipTranslate;
	private int lightTimes = 20;
	private int darkTimes = 60;
	private boolean selected = false;
	private boolean finalised = false;
	//
	@Value(optional = true)
	public int target = 0;
	@Value(optional = true)
	public int owner = -1;
	@Value(optional = true)
	public double baseDelay = 0.0;
	@Value
	public List<List<Integer>> waypoints;
	private int id = -1;

	public PathNormal() {
		init();
	}

	public PathNormal(float baseDelay, int targetPlayerId, int ownerPlayerId) {
		this.baseDelay = baseDelay;
		this.target = targetPlayerId;
		this.owner = ownerPlayerId;
		this.waypoints = new ArrayList<List<Integer>>();
		init();
	}

	private void init() {
		this.blipTranslate = new AffineTransform();
	}

	public void setGameState(GameState gs) {
		this.gs = gs;
	}

	public int getId() {
		throw new UnsupportedOperationException("Not supported yet.");
	}

	public void setId(int id) {
		this.id = id;
	}

	@Override
	public double getBaseDelay() {
		return this.baseDelay;
	}

	public void setBaseDelay(double d) {
		this.baseDelay = d;
	}

	public void addWaypoint(GridPoint p) {
		ArrayList<Integer> step = new ArrayList<Integer>();
		step.add(p.col);
		step.add(p.row);
		waypoints.add(step);
	}

	@Override
	public int length() {
		return this.steps.length;
	}

	@Override
	public Player getTarget() {
		return gs.getPlayer(target);
	}

	public void setTarget(int playerId) {
		target = playerId;
	}

	@Override
	public Player getOwner() {
		return gs.getPlayer(owner);
	}

	public void setOwner(int playerId) {
		owner = playerId;
	}

	@Override
	public Waypoint getWaypoint(int step) {
		if (step < 0) {
			step = 0;
		}
		if (step >= steps.length) {
			step = steps.length - 1;
		}
		return steps[step];
	}

	@Override
	public GridPoint getWaypointTileCoords(int step) {
		if (step < 0) {
			step = 0;
		}
		if (step >= steps.length) {
			step = steps.length - 1;
		}
		return new GridPoint(steps[step].gridPoint.col, steps[step].gridPoint.row);
	}

	@Override
	public Point2D.Double getWaypointGameCoords(int step) {
		if (this.steps.length == 0) {
			return new Double(0, 0);
		} else {
			if (step < 0) {
				step = 0;
			}
			if (step >= steps.length) {
				step = steps.length - 1;
			}
			Point2D.Double point = steps[step].gamePoint;
			return new Double(point.x, point.y);
		}
	}

	public void insertStep(int i, int x, int y) {
		ArrayList<Integer> step = new ArrayList<Integer>();
		step.add(x);
		step.add(y);
		waypoints.add(i, step);
		this.finalise();
	}

	public void updateStep(int i, int x, int y) {
		ArrayList<Integer> step = new ArrayList<Integer>();
		step.add(x);
		step.add(y);
		waypoints.remove(i);
		waypoints.add(i, step);
		this.finalise();
	}

	public void removeStep(int i) {
		waypoints.remove(i);
		this.finalise();
	}

	@Override
	public void finalise() {
		steps = new Waypoint[waypoints.size()];
		int i = 0;
		for (List<Integer> step : waypoints) {
			steps[i] = new Waypoint(step.get(0), step.get(1));
			i++;
		}

		int blipCount = this.length();
		this.blipStep = new int[blipCount];

		for (i = 0; i < blipStep.length; i++) {
			blipStep[i] = lightTimes + darkTimes - 1 - i % (lightTimes + darkTimes);
		}
		finalised = true;
		resetScale(canvas);
	}

	@Override
	public void paint(Graphics2D g2, int gameTime) {
		if (this.blipImage == null || this.steps.length == 0 || !finalised) {
			// Don't try to paint an empty path...
		} else {
			Stroke defaultStroke = g2.getStroke();

			if (selected) {

				Shape circle;
				g2.setColor(lineColor);
				g2.setStroke(this.stroke);
				GeneralPath poly1 = new GeneralPath(GeneralPath.WIND_EVEN_ODD, this.steps.length);
				Double point = this.steps[0].paintPoint;
				poly1.moveTo(point.x, point.y);
				for (int index = 0; index <= this.steps.length; index++) {
					point = steps[index].paintPoint;
					poly1.lineTo(point.x, point.y);
					circle = new Ellipse2D.Double(point.x - this.blipSize / 2, point.y - this.blipSize / 2, this.blipSize, this.blipSize);
					g2.draw(circle);
				}
				g2.draw(poly1);

			}

			for (int i = 0; i < this.blipStep.length; i++) {
				this.blipStep[i]++;
				if (this.blipStep[i] == lightTimes + darkTimes) {
					this.blipStep[i] = 0;
				}
				int image = this.blipStep[i];
				if (image > 0 && image < this.blipImage.length) {
					Double point1 = steps[i].paintPoint;
					double x, y;
					x = point1.x - this.blipSize / 2;
					y = point1.y - this.blipSize / 2;
					g2.drawImage(this.blipImage[image], (int) x, (int) y, null);
				}
			}
			g2.setStroke(defaultStroke);

		}
	}

	@Override
	public void resetScale(ScaledCanvas sc) {
		if (sc == null) {
			return;
		}
		canvas = sc;
		GameGrid gameGrid = gs.getGameGrid();
		for (Waypoint waypoint : steps) {
			gameGrid.getTileGameCoordinates(waypoint.gridPoint, waypoint.gamePoint);
			sc.fromGameToPaintCoordinates(waypoint.gamePoint, waypoint.paintPoint);
		}

		if (blipImage != null) {
			for (int i = 0; i < blipImage.length; i++) {
				if (blipImage[i] != null) {
					blipImage[i].flush();
				}
			}
		} else {
			blipImage = new BufferedImage[lightTimes];
		}

		stroke = new BasicStroke((float) sc.getDefaultStrokeWidth(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);

		blipSize = Math.max(1f, sc.getPaintScale() / 4f);
		blipShape = new Ellipse2D.Double(0, 0, blipSize, blipSize);
		float[] dist = {0.0f, 1f};
		int num = this.blipImage.length;
		for (int i = 0; i < num; i++) {
			int trans = Math.max(0, 255 - i * 255 / num);
			Color[] colors = {new Color(blipColor.getRed(), blipColor.getGreen(), blipColor.getBlue(), trans), blipBackColor};
			blipFill = new RadialGradientPaint((float) blipSize / 2, (float) blipSize / 2, (float) blipSize / 2.1f, dist, colors);

			blipImage[i] = new BufferedImage((int) Math.round(blipSize), (int) Math.round(blipSize), BufferedImage.TYPE_INT_ARGB_PRE);
			Graphics2D g2 = this.blipImage[i].createGraphics();
			g2.setPaint(blipFill);
			g2.fill(blipShape);
			g2.dispose();
		}
	}

	private void shiftUpDown(int to, int correction) {
		Integer x, y, shift;
		for (int i = 0; i < this.steps.length; i++) {
			//this.stepsY[i] += to;
			List<Integer> remove = waypoints.remove(i);
			x = remove.get(0);
			y = remove.get(1);

			shift = 0;
			boolean odd = ((y & 1) == 1);
			if (correction < 0) {
				odd = !odd;
			}
			if (odd) {
				shift = correction;
			}

			y += to;
			x += shift;
			List<Integer> add = new ArrayList<Integer>(2);
			add.add(x);
			add.add(y);
			waypoints.add(i, add);
		}
		this.finalise();
	}

	private void shiftLeftRight(int to) {
		Integer y;
		for (int i = 0; i < this.steps.length; i++) {
			List<Integer> get = waypoints.remove(i);
			List<Integer> add = new ArrayList<Integer>(2);
			add.add(get.get(0));
			add.add(get.get(1) + to);
			waypoints.add(i, add);
		}
		this.finalise();
	}

	public void shift(Constants.Direction direction, int amount, int correction) {
		switch (direction) {
			case UP:
			case NORTH:
				this.shiftUpDown(-amount, correction);
				break;
			case DOWN:
			case SOUTH:
				this.shiftUpDown(amount, correction);
				break;
			case RIGHT:
			case EAST:
				this.shiftLeftRight(amount);
				break;
			case LEFT:
			case WEST:
				this.shiftLeftRight(-amount);
				break;
		}
	}

	@Override
	public boolean isSelected() {
		return selected;
	}

	@Override
	public void setSelected(boolean selected) {
		this.selected = selected;
	}

	public PathLocationWaypoint getStartingPosition() {
		PathLocationWaypoint pathLocationWaypoint = new PathLocationWaypoint(this);
		return pathLocationWaypoint;
	}

	public void advance(PathLocation p, double speed) {
		PathLocationWaypoint plw = (PathLocationWaypoint) p;
		plw.update(speed);
	}
}
